@spyglassmc/core 0.4.19 → 0.4.21

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.
@@ -2,6 +2,7 @@ import type { Range } from '../source/index.js';
2
2
  import type { AstNode } from './AstNode.js';
3
3
  export interface RecordBaseNode<K extends AstNode, V extends AstNode> extends AstNode {
4
4
  readonly children: PairNode<K, V>[];
5
+ innerRange?: Range;
5
6
  }
6
7
  export interface RecordNode<K extends AstNode, V extends AstNode> extends RecordBaseNode<K, V> {
7
8
  type: 'record';
@@ -45,7 +45,20 @@ export function list({ start, value, sep, trailingSep, end }) {
45
45
  }
46
46
  // Trailing item sep.
47
47
  if (hasValueSep && !trailingSep) {
48
- ctx.err.report(localize('parser.list.trailing-sep'), ans.children[ans.children.length - 1].sep);
48
+ const trailingRange = ans.children[ans.children.length - 1].sep;
49
+ ctx.err.report(localize('parser.list.trailing-sep'), trailingRange, 3 /* ErrorSeverity.Error */, {
50
+ codeAction: {
51
+ title: localize('code-action.remove-trailing-separation'),
52
+ isPreferred: true,
53
+ changes: [
54
+ {
55
+ type: 'edit',
56
+ range: trailingRange,
57
+ text: '',
58
+ },
59
+ ],
60
+ },
61
+ });
49
62
  }
50
63
  // End.
51
64
  if (!src.trySkip(end)) {
@@ -9,6 +9,7 @@ export function record({ start, pair, end }) {
9
9
  return (src, ctx) => {
10
10
  const ans = { type: 'record', range: Range.create(src), children: [] };
11
11
  if (src.trySkip(start)) {
12
+ ans.innerRange = Range.create(src);
12
13
  src.skipWhitespace();
13
14
  let requiresPairEnd = false;
14
15
  let hasPairEnd = false;
@@ -80,9 +81,23 @@ export function record({ start, pair, end }) {
80
81
  }
81
82
  // Trailing pair end.
82
83
  if (hasPairEnd && !pair.trailingEnd) {
83
- ctx.err.report(localize('parser.record.trailing-end'), ans.children[ans.children.length - 1].end);
84
+ const trailingRange = ans.children[ans.children.length - 1].end;
85
+ ctx.err.report(localize('parser.record.trailing-end'), trailingRange, 3 /* ErrorSeverity.Error */, {
86
+ codeAction: {
87
+ title: localize('code-action.remove-trailing-separation'),
88
+ isPreferred: true,
89
+ changes: [
90
+ {
91
+ type: 'edit',
92
+ range: trailingRange,
93
+ text: '',
94
+ },
95
+ ],
96
+ },
97
+ });
84
98
  }
85
99
  // End.
100
+ ans.innerRange.end = src.cursor;
86
101
  if (!src.trySkip(end)) {
87
102
  ctx.err.report(localize('expected', localeQuote(end)), src);
88
103
  }
@@ -104,7 +104,20 @@ export function resourceLocation(options) {
104
104
  ctx.err.report(localize('parser.resource-location.tag-required'), ans);
105
105
  }
106
106
  if (!ans.namespace && options.requireCanonical) {
107
- ctx.err.report(localize('parser.resource-location.namespace-expected'), ans);
107
+ ctx.err.report(localize('parser.resource-location.namespace-expected'), ans, 3 /* ErrorSeverity.Error */, {
108
+ codeAction: {
109
+ title: localize('code-action.add-default-namespace'),
110
+ isPreferred: true,
111
+ changes: [
112
+ {
113
+ type: 'edit',
114
+ range: Range.create(start),
115
+ text: ResourceLocation.DefaultNamespace
116
+ + ResourceLocation.NamespacePathSep,
117
+ },
118
+ ],
119
+ },
120
+ });
108
121
  }
109
122
  }
110
123
  return ans;
@@ -0,0 +1,8 @@
1
+ import type { AstNode } from '../../node/index.js';
2
+ import type { CodeActionProviderContext } from '../../service/index.js';
3
+ import type { LanguageError, LanguageErrorAction } from '../../source/index.js';
4
+ export interface CodeAction extends LanguageErrorAction {
5
+ errors?: LanguageError[];
6
+ }
7
+ export type CodeActionProvider<N extends AstNode = AstNode> = (node: N, ctx: CodeActionProviderContext) => readonly CodeAction[];
8
+ //# sourceMappingURL=CodeAction.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=CodeAction.js.map
@@ -0,0 +1,8 @@
1
+ import type { AstNode } from '../../node/index.js';
2
+ import { FileNode } from '../../node/index.js';
3
+ import type { MetaRegistry } from '../../service/index.js';
4
+ import type { CodeActionProvider } from './CodeAction.js';
5
+ export declare const fallback: CodeActionProvider<AstNode>;
6
+ export declare const file: CodeActionProvider<FileNode<AstNode>>;
7
+ export declare function registerProviders(meta: MetaRegistry): void;
8
+ //# sourceMappingURL=builtin.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { FileNode } from '../../node/index.js';
2
+ import { Range } from '../../source/index.js';
3
+ import { traversePreOrder } from '../util.js';
4
+ export const fallback = (node, ctx) => {
5
+ const ans = [];
6
+ traversePreOrder(node, (node) => Range.containsRange(node.range, ctx.range, true), (node) => ctx.meta.hasCodeActionProvider(node.type), (node) => ans.push(...ctx.meta.getCodeActionProvider(node.type)(node, ctx)));
7
+ return ans;
8
+ };
9
+ export const file = (node, ctx) => {
10
+ const ans = [];
11
+ for (const error of FileNode.getErrors(node)) {
12
+ const action = error.info?.codeAction;
13
+ if (!action) {
14
+ continue;
15
+ }
16
+ if (!Range.containsRange(error.range, ctx.range, true)) {
17
+ continue;
18
+ }
19
+ ans.push({
20
+ ...action,
21
+ errors: [error],
22
+ });
23
+ }
24
+ return ans;
25
+ };
26
+ export function registerProviders(meta) {
27
+ meta.registerCodeActionProvider('file', file);
28
+ }
29
+ //# sourceMappingURL=builtin.js.map
@@ -0,0 +1,3 @@
1
+ export * as codeActions from './builtin.js';
2
+ export * from './CodeAction.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * as codeActions from './builtin.js';
2
+ export * from './CodeAction.js';
3
+ //# sourceMappingURL=index.js.map
@@ -62,7 +62,7 @@ const prefixed = (node, ctx) => {
62
62
  };
63
63
  export function record(o) {
64
64
  return (node, ctx) => {
65
- if (!Range.contains(Range.translate(node, 1, -1), ctx.offset, true)) {
65
+ if (!node.innerRange || !Range.contains(node.innerRange, ctx.offset, true)) {
66
66
  return [];
67
67
  }
68
68
  const completeKeys = (pair) => o.key(node, pair, ctx, pair?.key ?? ctx.offset, false, false, existingKeys);
@@ -1,5 +1,6 @@
1
1
  export * from './binder/index.js';
2
2
  export * from './checker/index.js';
3
+ export * from './codeActions/index.js';
3
4
  export * from './ColorInfoProvider.js';
4
5
  export * from './colorizer/index.js';
5
6
  export * from './completer/index.js';
@@ -1,5 +1,6 @@
1
1
  export * from './binder/index.js';
2
2
  export * from './checker/index.js';
3
+ export * from './codeActions/index.js';
3
4
  export * from './ColorInfoProvider.js';
4
5
  export * from './colorizer/index.js';
5
6
  export * from './completer/index.js';
@@ -14,10 +14,21 @@ export const undeclaredSymbol = (node, ctx) => {
14
14
  });
15
15
  }
16
16
  if (Config.Action.isReport(action)) {
17
+ const info = {};
18
+ const uriBuilder = ctx.meta.getUriBuilder(node.symbol.category);
19
+ if (uriBuilder) {
20
+ const uri = uriBuilder(node.symbol.identifier, ctx);
21
+ if (uri) {
22
+ info.codeAction = {
23
+ title: localize('code-action.create-undeclared-file', node.symbol.category, localeQuote(node.symbol.identifier)),
24
+ changes: [{ type: 'create', uri }],
25
+ };
26
+ }
27
+ }
17
28
  const severityOverride = action.report === 'inherit'
18
29
  ? undefined
19
30
  : LinterSeverity.toErrorSeverity(action.report);
20
- ctx.err.lint(localize('linter.undeclared-symbol.message', node.symbol.category, localeQuote(node.symbol.identifier)), node, undefined, severityOverride);
31
+ ctx.err.lint(localize('linter.undeclared-symbol.message', node.symbol.category, localeQuote(node.symbol.identifier)), node, info, severityOverride);
21
32
  }
22
33
  };
23
34
  function getAction(config, symbol, ctx) {
@@ -130,8 +130,8 @@ export class CacheService {
130
130
  ans.unchangedFiles.push(uri);
131
131
  continue;
132
132
  }
133
- if (this.project.ignore.ignores(uri)) {
134
- ans.unchangedFiles.push(uri);
133
+ if (this.project.shouldExclude(uri)) {
134
+ ans.removedFiles.push(uri);
135
135
  continue;
136
136
  }
137
137
  try {
@@ -59,7 +59,7 @@ export interface EnvConfig {
59
59
  */
60
60
  dependencies: string[];
61
61
  /**
62
- * A list of file patterns to exclude. Each value in this array can either be a glob pattern or the special string `@gitignore`.
62
+ * A list of file patterns to exclude.
63
63
  */
64
64
  exclude: string[];
65
65
  /**
@@ -107,7 +107,11 @@ export const VanillaConfig = {
107
107
  env: {
108
108
  dataSource: 'GitHub',
109
109
  dependencies: ['@vanilla-datapack', '@vanilla-resourcepack', '@vanilla-mcdoc'],
110
- exclude: ['@gitignore', '.vscode/', '.github/'],
110
+ exclude: [
111
+ '.*/**',
112
+ '**/node_modules/**',
113
+ '**/__pycache__/**',
114
+ ],
111
115
  customResources: {},
112
116
  feature: {
113
117
  codeActions: true,
@@ -112,6 +112,15 @@ interface FormatterContextOptions extends ProcessorContextOptions {
112
112
  export declare namespace FormatterContext {
113
113
  function create(project: ProjectData, opts: FormatterContextOptions): FormatterContext;
114
114
  }
115
+ export interface CodeActionProviderContext extends ProcessorContext {
116
+ range: Range;
117
+ }
118
+ export interface CodeActionProviderContextOptions extends ProcessorContextOptions {
119
+ range: Range;
120
+ }
121
+ export declare namespace CodeActionProviderContext {
122
+ function create(project: ProjectData, opts: CodeActionProviderContextOptions): CodeActionProviderContext;
123
+ }
115
124
  export interface ColorizerContext extends ProcessorWithRangeContext {
116
125
  }
117
126
  export interface ColorizerContextOptions extends ProcessorWithRangeContextOptions {
@@ -104,6 +104,13 @@ export var FormatterContext;
104
104
  }
105
105
  FormatterContext.create = create;
106
106
  })(FormatterContext || (FormatterContext = {}));
107
+ export var CodeActionProviderContext;
108
+ (function (CodeActionProviderContext) {
109
+ function create(project, opts) {
110
+ return { ...ProcessorContext.create(project, opts), range: opts.range };
111
+ }
112
+ CodeActionProviderContext.create = create;
113
+ })(CodeActionProviderContext || (CodeActionProviderContext = {}));
107
114
  export var ColorizerContext;
108
115
  (function (ColorizerContext) {
109
116
  function create(project, opts) {
@@ -3,12 +3,13 @@ import { Lazy } from '../common/index.js';
3
3
  import type { AstNode } from '../node/index.js';
4
4
  import type { Parser } from '../parser/index.js';
5
5
  import type { Formatter } from '../processor/formatter/index.js';
6
- import type { Binder, Checker, Colorizer, Completer, InlayHintProvider } from '../processor/index.js';
6
+ import type { Binder, Checker, CodeActionProvider, Colorizer, Completer, InlayHintProvider } from '../processor/index.js';
7
7
  import type { Linter } from '../processor/linter/Linter.js';
8
8
  import type { SignatureHelpProvider } from '../processor/SignatureHelpProvider.js';
9
9
  import type { DependencyKey, DependencyProvider } from './Dependency.js';
10
10
  import type { FileExtension } from './fileUtil.js';
11
11
  import type { SymbolRegistrar } from './SymbolRegistrar.js';
12
+ import type { UriBuilder } from './UriBuilder.js';
12
13
  import type { UriBinder, UriSorter, UriSorterRegistration } from './UriProcessor.js';
13
14
  export interface LanguageOptions {
14
15
  /**
@@ -69,6 +70,9 @@ export declare class MetaRegistry {
69
70
  hasChecker<N extends AstNode>(type: N['type']): boolean;
70
71
  getChecker<N extends AstNode>(type: N['type']): Checker<N>;
71
72
  registerChecker<N extends AstNode>(type: N['type'], checker: Checker<N>): void;
73
+ hasCodeActionProvider<N extends AstNode>(type: N['type']): boolean;
74
+ getCodeActionProvider<N extends AstNode>(type: N['type']): CodeActionProvider<N>;
75
+ registerCodeActionProvider<N extends AstNode>(type: N['type'], codeActionProvider: CodeActionProvider<N>): void;
72
76
  hasColorizer<N extends AstNode>(type: N['type']): boolean;
73
77
  getColorizer<N extends AstNode>(type: N['type']): Colorizer<N>;
74
78
  registerColorizer<N extends AstNode>(type: N['type'], colorizer: Colorizer<N>): void;
@@ -111,6 +115,9 @@ export declare class MetaRegistry {
111
115
  getCustom<T>(group: string): Map<string, T> | undefined;
112
116
  registerUriBinder(uriBinder: UriBinder): void;
113
117
  get uriBinders(): Set<UriBinder>;
118
+ hasUriBuilder(category: string): boolean;
119
+ getUriBuilder(category: string): UriBuilder | undefined;
120
+ registerUriBuilder(category: string, builder: UriBuilder): void;
114
121
  setUriSorter(uriSorter: UriSorterRegistration): void;
115
122
  get uriSorter(): UriSorter;
116
123
  }
@@ -1,5 +1,5 @@
1
1
  import { Lazy } from '../common/index.js';
2
- import { binder, checker, colorizer, completer, formatter, linter } from '../processor/index.js';
2
+ import { binder, checker, codeActions, colorizer, completer, formatter, linter, } from '../processor/index.js';
3
3
  /* istanbul ignore next */
4
4
  /**
5
5
  * The meta registry of Spyglass. You can register new parsers, processors, and languages here.
@@ -12,6 +12,7 @@ export class MetaRegistry {
12
12
  #binders = new Map();
13
13
  #checkers = new Map();
14
14
  #colorizers = new Map();
15
+ #codeActionProviders = new Map();
15
16
  #completers = new Map();
16
17
  #dependencyProviders = new Map();
17
18
  #formatters = new Map();
@@ -22,10 +23,12 @@ export class MetaRegistry {
22
23
  #symbolRegistrars = new Map();
23
24
  #custom = new Map();
24
25
  #uriBinders = new Set();
26
+ #uriBuilders = new Map();
25
27
  #uriSorter = () => 0;
26
28
  constructor() {
27
29
  binder.registerBinders(this);
28
30
  checker.registerCheckers(this);
31
+ codeActions.registerProviders(this);
29
32
  colorizer.registerColorizers(this);
30
33
  completer.registerCompleters(this);
31
34
  formatter.registerFormatters(this);
@@ -90,6 +93,15 @@ export class MetaRegistry {
90
93
  registerChecker(type, checker) {
91
94
  this.#checkers.set(type, checker);
92
95
  }
96
+ hasCodeActionProvider(type) {
97
+ return this.#codeActionProviders.has(type);
98
+ }
99
+ getCodeActionProvider(type) {
100
+ return this.#codeActionProviders.get(type) ?? codeActions.fallback;
101
+ }
102
+ registerCodeActionProvider(type, codeActionProvider) {
103
+ this.#codeActionProviders.set(type, codeActionProvider);
104
+ }
93
105
  hasColorizer(type) {
94
106
  return this.#colorizers.has(type);
95
107
  }
@@ -198,6 +210,15 @@ export class MetaRegistry {
198
210
  get uriBinders() {
199
211
  return this.#uriBinders;
200
212
  }
213
+ hasUriBuilder(category) {
214
+ return this.#uriBuilders.has(category);
215
+ }
216
+ getUriBuilder(category) {
217
+ return this.#uriBuilders.get(category);
218
+ }
219
+ registerUriBuilder(category, builder) {
220
+ this.#uriBuilders.set(category, builder);
221
+ }
201
222
  setUriSorter(uriSorter) {
202
223
  const nextSorter = this.#uriSorter;
203
224
  this.#uriSorter = (a, b) => uriSorter(a, b, nextSorter);
@@ -1,4 +1,3 @@
1
- import type { Ignore } from 'ignore';
2
1
  import type { TextDocumentContentChangeEvent } from 'vscode-languageserver-textdocument';
3
2
  import { TextDocument } from 'vscode-languageserver-textdocument';
4
3
  import type { ExternalEventEmitter, Externals } from '../common/index.js';
@@ -98,11 +97,9 @@ export type ProjectData = Pick<Project, 'cacheRoot' | 'config' | 'downloader' |
98
97
  export declare class Project implements ExternalEventEmitter {
99
98
  #private;
100
99
  private static readonly RootSuffix;
101
- private static readonly GitIgnore;
102
100
  readonly cacheService: CacheService;
103
101
  get isReady(): boolean;
104
102
  config: Config;
105
- ignore: Ignore;
106
103
  readonly downloader: Downloader;
107
104
  readonly externals: Externals;
108
105
  readonly fs: FileService;
@@ -161,7 +158,6 @@ export declare class Project implements ExternalEventEmitter {
161
158
  getTrackedFiles(): string[];
162
159
  constructor({ cacheRoot, defaultConfig, downloader, externals, fs, initializers, isDebugging, logger, profilers, projectRoots, }: ProjectOptions);
163
160
  private setInitPromise;
164
- private readGitignore;
165
161
  private setReadyPromise;
166
162
  /**
167
163
  * Load the config file and initialize parsers and processors.
@@ -203,6 +199,7 @@ export declare class Project implements ExternalEventEmitter {
203
199
  ensureClientManagedChecked(uri: string): Promise<DocAndNode | undefined>;
204
200
  getClientManaged(uri: string): DocAndNode | undefined;
205
201
  showCacheRoot(): Promise<void>;
202
+ shouldExclude(uri: string): boolean;
206
203
  private tryClearingCache;
207
204
  private shouldRemove;
208
205
  private isOnlyWatched;
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import ignore from 'ignore';
7
+ import picomatch from 'picomatch';
8
8
  import { TextDocument } from 'vscode-languageserver-textdocument';
9
9
  import { bufferToString, Logger, normalizeUri, SingletonPromise, StateProxy, } from '../common/index.js';
10
10
  import { FileNode } from '../node/index.js';
@@ -63,7 +63,6 @@ const CacheAutoSaveInterval = 600_000; // 10 Minutes.
63
63
  */
64
64
  export class Project {
65
65
  static RootSuffix = '/pack.mcmeta';
66
- static GitIgnore = '.gitignore';
67
66
  /** Prevent circular binding. */
68
67
  #bindingInProgressUris = new Set();
69
68
  #cacheSaverIntervalId;
@@ -85,7 +84,6 @@ export class Project {
85
84
  return this.#isReady;
86
85
  }
87
86
  config;
88
- ignore = ignore();
89
87
  downloader;
90
88
  externals;
91
89
  fs;
@@ -159,9 +157,7 @@ export class Project {
159
157
  const supportedFiles = [...this.#dependencyFiles ?? [], ...this.#watchedFiles]
160
158
  .filter((file) => extensions.includes(fileUtil.extname(file) ?? ''));
161
159
  this.logger.info(`[Project#getTrackedFiles] Listed ${supportedFiles.length} supported files`);
162
- const filteredFiles = this.ignore.filter(supportedFiles);
163
- this.logger.info(`[Project#getTrackedFiles] After ignoring, keeping ${filteredFiles.length} tracked files`);
164
- return filteredFiles;
160
+ return supportedFiles;
165
161
  }
166
162
  constructor({ cacheRoot, defaultConfig, downloader, externals, fs = FileService.create(externals, cacheRoot), initializers = [], isDebugging = false, logger = Logger.create(), profilers = ProfilerFactory.noop(), projectRoots, }) {
167
163
  this.#cacheRoot = cacheRoot;
@@ -227,21 +223,6 @@ export class Project {
227
223
  });
228
224
  }
229
225
  setInitPromise() {
230
- const loadConfig = async () => {
231
- this.config = await this.#configService.load();
232
- this.ignore = ignore();
233
- for (const pattern of this.config.env.exclude) {
234
- if (pattern === '@gitignore') {
235
- const gitignore = await this.readGitignore();
236
- if (gitignore) {
237
- this.ignore.add(gitignore);
238
- }
239
- }
240
- else {
241
- this.ignore.add(pattern);
242
- }
243
- }
244
- };
245
226
  const callIntializers = async () => {
246
227
  const initCtx = {
247
228
  cacheRoot: this.cacheRoot,
@@ -271,29 +252,13 @@ export class Project {
271
252
  this.symbols = new SymbolUtil(symbols, this.externals.event.EventEmitter);
272
253
  this.symbols.buildCache();
273
254
  __profiler.task('Load Cache');
274
- await loadConfig();
255
+ this.config = await this.#configService.load();
275
256
  __profiler.task('Load Config');
276
257
  await callIntializers();
277
258
  __profiler.task('Initialize').finalize();
278
259
  };
279
260
  this.#initPromise = init();
280
261
  }
281
- async readGitignore() {
282
- if (this.projectRoots.length === 0) {
283
- return undefined;
284
- }
285
- try {
286
- const uri = this.projectRoots[0] + Project.GitIgnore;
287
- const contents = await this.externals.fs.readFile(uri);
288
- return bufferToString(contents);
289
- }
290
- catch (e) {
291
- if (!this.externals.error.isKind(e, 'ENOENT')) {
292
- this.logger.error(`[Project] [readGitignore]`, e);
293
- }
294
- }
295
- return undefined;
296
- }
297
262
  setReadyPromise() {
298
263
  const getDependencies = async () => {
299
264
  const ans = [];
@@ -339,15 +304,24 @@ export class Project {
339
304
  this.#watcherReady = true;
340
305
  resolve();
341
306
  }).on('add', (uri) => {
307
+ if (this.shouldExclude(uri)) {
308
+ return;
309
+ }
342
310
  this.#watchedFiles.add(uri);
343
311
  if (this.#watcherReady) {
344
312
  this.emit('fileCreated', { uri });
345
313
  }
346
314
  }).on('change', (uri) => {
315
+ if (this.shouldExclude(uri)) {
316
+ return;
317
+ }
347
318
  if (this.#watcherReady) {
348
319
  this.emit('fileModified', { uri });
349
320
  }
350
321
  }).on('unlink', (uri) => {
322
+ if (this.shouldExclude(uri)) {
323
+ return;
324
+ }
351
325
  this.#watchedFiles.delete(uri);
352
326
  if (this.#watcherReady) {
353
327
  this.emit('fileDeleted', { uri });
@@ -671,6 +645,9 @@ export class Project {
671
645
  if (!fileUtil.isFileUri(uri)) {
672
646
  return; // We only accept `file:` scheme for client-managed URIs.
673
647
  }
648
+ if (this.shouldExclude(uri)) {
649
+ return;
650
+ }
674
651
  const doc = TextDocument.create(uri, languageID, version, content);
675
652
  const node = this.parse(doc);
676
653
  this.#clientManagedUris.add(uri);
@@ -690,6 +667,9 @@ export class Project {
690
667
  if (!fileUtil.isFileUri(uri)) {
691
668
  return; // We only accept `file:` scheme for client-managed URIs.
692
669
  }
670
+ if (this.shouldExclude(uri)) {
671
+ return;
672
+ }
693
673
  const doc = this.#clientManagedDocAndNodes.get(uri)?.doc;
694
674
  if (!doc) {
695
675
  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?`);
@@ -744,6 +724,17 @@ export class Project {
744
724
  this.logger.error('[Service#showCacheRoot]', e);
745
725
  }
746
726
  }
727
+ shouldExclude(uri) {
728
+ if (this.config.env.exclude.length === 0) {
729
+ return false;
730
+ }
731
+ for (const rel of fileUtil.getRels(uri, this.projectRoots)) {
732
+ if (picomatch(this.config.env.exclude, { dot: true, posixSlashes: false })(rel)) {
733
+ return true;
734
+ }
735
+ }
736
+ return false;
737
+ }
747
738
  tryClearingCache(uri) {
748
739
  if (this.shouldRemove(uri)) {
749
740
  this.removeCachedTextDocument(uri);
@@ -2,7 +2,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
2
2
  import type { Logger } from '../common/index.js';
3
3
  import type { FileNode } from '../node/index.js';
4
4
  import { AstNode } from '../node/index.js';
5
- import type { Color, ColorInfo, ColorToken, InlayHint, SignatureHelp } from '../processor/index.js';
5
+ import type { CodeAction, Color, ColorInfo, ColorToken, InlayHint, SignatureHelp } from '../processor/index.js';
6
6
  import { ColorPresentation } from '../processor/index.js';
7
7
  import { Range } from '../source/index.js';
8
8
  import type { SymbolUsageType } from '../symbol/index.js';
@@ -25,6 +25,7 @@ export declare class Service {
25
25
  constructor({ isDebugging, logger, profilers, project }: Options);
26
26
  private debug;
27
27
  colorize(node: FileNode<AstNode>, doc: TextDocument, range?: Range): readonly ColorToken[];
28
+ getCodeActions(node: FileNode<AstNode>, doc: TextDocument, range: Range): readonly CodeAction[];
28
29
  getColorInfo(node: FileNode<AstNode>, doc: TextDocument): ColorInfo[];
29
30
  getColorPresentation(file: FileNode<AstNode>, doc: TextDocument, range: Range, color: Color): ColorPresentation[];
30
31
  complete(node: FileNode<AstNode>, doc: TextDocument, offset: number, triggerCharacter?: string): import("../processor/index.js").CompletionItem[];
@@ -2,7 +2,7 @@ import { AstNode } from '../node/index.js';
2
2
  import { ColorPresentation, completer, traversePreOrder } from '../processor/index.js';
3
3
  import { Range } from '../source/index.js';
4
4
  import { SymbolUsageTypes } from '../symbol/index.js';
5
- import { ColorizerContext, CompleterContext, FormatterContext, ProcessorContext, SignatureHelpProviderContext, } from './Context.js';
5
+ import { CodeActionProviderContext, ColorizerContext, CompleterContext, FormatterContext, ProcessorContext, SignatureHelpProviderContext, } from './Context.js';
6
6
  import { fileUtil } from './fileUtil.js';
7
7
  import { Hover } from './Hover.js';
8
8
  import { ProfilerFactory } from './Profiler.js';
@@ -36,6 +36,18 @@ export class Service {
36
36
  }
37
37
  return [];
38
38
  }
39
+ getCodeActions(node, doc, range) {
40
+ try {
41
+ this.debug(`Getting code actions ${doc.uri} # ${doc.version} @ ${Range.toString(range)}`);
42
+ const codeActionProvider = this.project.meta.getCodeActionProvider(node.type);
43
+ const ctx = CodeActionProviderContext.create(this.project, { doc, range });
44
+ return codeActionProvider(node, ctx);
45
+ }
46
+ catch (e) {
47
+ this.logger.error(`[Service] [getCodeActions] Failed for ${doc.uri} # ${doc.version}`, e);
48
+ }
49
+ return [];
50
+ }
39
51
  getColorInfo(node, doc) {
40
52
  try {
41
53
  this.debug(`Getting color info for ${doc.uri} # ${doc.version}`);
@@ -0,0 +1,11 @@
1
+ import type { TextDocument } from 'vscode-languageserver-textdocument';
2
+ import type { RootUriString } from '../service/index.js';
3
+ import type { Config } from './Config.js';
4
+ export type UriBuilder = (identifier: string, ctx: UriBuilderContext) => string | undefined;
5
+ export interface UriBuilderContext {
6
+ doc: TextDocument;
7
+ project: Record<string, string>;
8
+ roots: readonly RootUriString[];
9
+ config: Config;
10
+ }
11
+ //# sourceMappingURL=UriBuilder.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=UriBuilder.js.map
@@ -31,6 +31,8 @@ export declare namespace fileUtil {
31
31
  * getRel(['file:///root/foo/', 'file:///root/'], 'file:///outsider.json') // -> undefined
32
32
  */
33
33
  function getRel(uri: string, rootUris: readonly RootUriString[]): string | undefined;
34
+ function getRoots(uri: string, rootUris: readonly RootUriString[]): Generator<RootUriString, undefined, unknown>;
35
+ function getRoot(uri: string, rootUris: readonly RootUriString[]): string | undefined;
34
36
  function isRootUri(uri: string): uri is RootUriString;
35
37
  function ensureEndingSlash(uri: string): RootUriString;
36
38
  function join(fromUri: string, toUri: string): string;
@@ -60,6 +60,20 @@ export var fileUtil;
60
60
  return getRels(uri, rootUris).next().value;
61
61
  }
62
62
  fileUtil.getRel = getRel;
63
+ function* getRoots(uri, rootUris) {
64
+ for (const root of rootUris) {
65
+ const rel = getRelativeUriFromBase(uri, root);
66
+ if (rel !== undefined) {
67
+ yield root;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+ fileUtil.getRoots = getRoots;
73
+ function getRoot(uri, rootUris) {
74
+ return getRoots(uri, rootUris).next().value;
75
+ }
76
+ fileUtil.getRoot = getRoot;
63
77
  function isRootUri(uri) {
64
78
  return uri.endsWith('/');
65
79
  }
@@ -13,5 +13,6 @@ export * from './Project.js';
13
13
  export * from './Service.js';
14
14
  export * from './SymbolLocations.js';
15
15
  export * from './SymbolRegistrar.js';
16
+ export * from './UriBuilder.js';
16
17
  export * from './UriProcessor.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -14,5 +14,6 @@ export * from './Project.js';
14
14
  export * from './Service.js';
15
15
  export * from './SymbolLocations.js';
16
16
  export * from './SymbolRegistrar.js';
17
+ export * from './UriBuilder.js';
17
18
  export * from './UriProcessor.js';
18
19
  //# sourceMappingURL=index.js.map
@@ -31,7 +31,7 @@ export declare const enum ErrorSeverity {
31
31
  Error = 3
32
32
  }
33
33
  export interface LanguageErrorInfo {
34
- codeAction?: string;
34
+ codeAction?: LanguageErrorAction;
35
35
  deprecated?: boolean;
36
36
  unnecessary?: boolean;
37
37
  related?: {
@@ -39,4 +39,17 @@ export interface LanguageErrorInfo {
39
39
  message: string;
40
40
  }[];
41
41
  }
42
+ export interface LanguageErrorAction {
43
+ title: string;
44
+ isPreferred?: boolean;
45
+ changes?: CodeActionChange[];
46
+ }
47
+ export type CodeActionChange = {
48
+ type: 'edit';
49
+ range: Range;
50
+ text: string;
51
+ } | {
52
+ type: 'create';
53
+ uri: string;
54
+ };
42
55
  //# sourceMappingURL=LanguageError.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spyglassmc/core",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -19,16 +19,17 @@
19
19
  "chokidar": "^3.5.2",
20
20
  "decompress": "^4.2.1",
21
21
  "follow-redirects": "^1.14.8",
22
- "ignore": "^5.3.1",
22
+ "picomatch": "^4.0.2",
23
23
  "pako": "^2.0.4",
24
24
  "rfdc": "^1.3.0",
25
25
  "vscode-languageserver-textdocument": "^1.0.4",
26
26
  "whatwg-url": "^14.0.0",
27
- "@spyglassmc/locales": "0.3.10"
27
+ "@spyglassmc/locales": "0.3.12"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/decompress": "^4.2.3",
31
31
  "@types/follow-redirects": "^1.14.1",
32
+ "@types/picomatch": "^3.0.1",
32
33
  "@types/pako": "^2.0.0",
33
34
  "@types/whatwg-url": "^11.0.4"
34
35
  },